package il.ac.bgu.cs.heuristics.core;

import il.ac.bgu.cs.heuristics.graph.State;
import il.ac.bgu.cs.heuristics.graph.types.IActionGraph;
import il.ac.bgu.cs.heuristics.graph.types.IPath;
import il.ac.bgu.cs.heuristics.graph.types.IState;
import il.ac.bgu.cs.heuristics.graph.types.ISubstitution;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;

public class HeuristicEngineLightIterativeWithDecisionNodes extends
		heuristicsEngine implements IHeuristicsEngine {

	private IState			init	= null;
	private IDecisionMaker	dm		= null;

	public HeuristicEngineLightIterativeWithDecisionNodes(IActionGraph aGraph) {
		super(aGraph);
		// dm = new SimpleDecisionMaker();
		dm = new DecisionMakerNoOneEffectIncludeAllGoalProposition();
		// TODO Auto-generated constructor stub
	}

	@Override
	public int getHValue(IState init, IState goal) {
		this.init = init;
		aGraph.setInitState(init);
		return extractLightHeuristicIterativeWithDecisionNodes(goal);
	}

	private int extractLightHeuristicIterativeWithDecisionNodes(IState goal) {
		Queue<SearchNodeWithData> queue = new LinkedList<SearchNodeWithData>();
		Set<SearchNodeWithData> decisionNodesMap = CollectionUtils
				.newLinkedHashSet();

		// remove fact which exsit in init
		IState newCleanGoal = State.createState(init
				.getNotContainedSubstitution(goal.getSubstitutions()));
		SearchNodeWithData originalGoal = new SearchNodeWithData(newCleanGoal,
				null, true, null, 0);
		originalGoal.parentNode = originalGoal;
		queue.add(originalGoal);
		return searchSpace(queue, decisionNodesMap);
	}

	private int searchSpace(Queue<SearchNodeWithData> queue,
			Set<SearchNodeWithData> decisionNodesMap) {
		SearchNodeWithData currentNode = null;
		while (!queue.isEmpty()) {
			currentNode = queue.poll();

			if (init.contains(currentNode.getSubgoal()))
				return propogateSuccess(currentNode);

			Set<IState> subgoals = aGraph
					.getNonContradictingEffects(currentNode.getSubgoal());
			subgoals = filterGoals(init, currentNode.getSubgoal(), subgoals,
					new NotUsefulGoalsStateFilter());
			currentNode.isFork = dm.isForkNode(currentNode, subgoals);
			Set<SearchNodeWithData> childNodes = createChildNodesAcordingToDecistion(
					currentNode, subgoals);
			queue.addAll(childNodes);
		}
		return 0;

	}

	private int propogateSuccess(SearchNodeWithData currentNode) {
		return OneChildDone(currentNode.getParentNodeAndAddHvalue(),
				currentNode);
	}

	private int OneChildDone(SearchNodeWithData parentNode,
			SearchNodeWithData childNode) {

		if (parentNode.isFork) {
			Set<ISubstitution> newGoalSubs = CollectionUtils.newLinkedHashSet();
			for (ISubstitution sub : parentNode.subgoal.getSubstitutions()) {
				if (!childNode.firstEffect.contains(sub))
					newGoalSubs.add(sub);
			}
			if (newGoalSubs.isEmpty()) {
				parentNode.isFork = false;
			} else {
				return extractLightHeuristicIterativeWithDecisionNodes(State
						.createState(newGoalSubs));
			}
			;

		}
		if (parentNode.firstEffect == null) // this mean we have the original
		// goal
		{
			return childNode.parentNode.getCurrentDistanceToGoal();
		} else {
			return OneChildDone(parentNode.parentNode, parentNode);
		}
	}

	protected Set<SearchNodeWithData> createChildNodesAcordingToDecistion(
			SearchNodeWithData currentNode, Set<IState> subgoals) {

		Set<SearchNodeWithData> childSet = CollectionUtils.newLinkedHashSet();
		for (IState subgoal : subgoals) {
			IPath subgoalPath = aGraph.getShortestPath(init, subgoal);
			Set<ISubstitution> ConstraintsList = subgoalPath.getConstraints();
			ConstraintsList = init.getNotContainedSubstitution(ConstraintsList);
			if (currentNode.isFork) {
				childSet.add(new SearchNodeWithData(ConstraintsList,
						currentNode, false, subgoal,
						currentNode.currentDistanceToGoal
								+ subgoalPath.weight()));

			} else {
				childSet.add(new SearchNodeWithData(ConstraintsList,
						currentNode.parentNode, false, currentNode.firstEffect,
						currentNode.currentDistanceToGoal
								+ subgoalPath.weight()));
			}

		}
		return childSet;
	}

	private class SearchNodeWithData {
		private IState				subgoal					= null;
		private int					currentDistanceToGoal	= 0;
		private IState				firstEffect				= null;
		private SearchNodeWithData	parentNode				= null;
		boolean						isFork					= false;

		public IState getSubgoal() {
			return subgoal;
		}

		public int getCurrentDistanceToGoal() {
			return currentDistanceToGoal;
		}

		private void setMembers(IState subgoal, SearchNodeWithData parent,
				boolean isFork, IState firstEffect, int currentDistanceToGoal) {
			this.parentNode = parent;
			this.subgoal = subgoal;
			this.currentDistanceToGoal = currentDistanceToGoal;
			this.firstEffect = firstEffect;
			this.isFork = isFork;

		}

		public SearchNodeWithData(IState subgoal, SearchNodeWithData parent,
				boolean isFork, IState firstEffect, int currentDistanceToGoal) {
			setMembers(subgoal, parent, isFork, firstEffect,
					currentDistanceToGoal);
		}

		public SearchNodeWithData(Set<ISubstitution> ConstraintList,
				SearchNodeWithData parent, boolean isFork, IState firstEffect,
				int currentDistanceToGoal) {
			setMembers(State.createState(ConstraintList), parent, isFork,
					firstEffect, currentDistanceToGoal);
		}

		public SearchNodeWithData getParentNodeAndAddHvalue() {
			if (parentNode == null)
				throw new IllegalArgumentException("parent node is null");
			parentNode.currentDistanceToGoal += this.currentDistanceToGoal;
			return parentNode;

		}

		@Override
		public String toString() {
			return subgoal.toString() + "\ncurrentDistanceToGoal: = "
					+ currentDistanceToGoal;

		}

	}

	interface IDecisionMaker {
		boolean isForkNode(SearchNodeWithData parentNode, Set<IState> subgoals);

	}

	private static class DecisionMakerNoOneEffectIncludeAllGoalProposition
			implements IDecisionMaker {

		static int	upperBoundCounter			= 0;
		static int	upperBoundOnDecisionNode	= 20;

		@Override
		public boolean isForkNode(SearchNodeWithData parentNode,
				Set<IState> subgoals) {
			if (parentNode.firstEffect == null)
				return true;
			if (upperBoundCounter >= upperBoundOnDecisionNode)
				return false;

			for (IState subgoal : subgoals) {
				if (subgoal.contains(parentNode.subgoal))
					return false;

			}
			upperBoundCounter++;
			return true;
		}

	}

	private class SimpleDecisionMaker implements IDecisionMaker {
		@Override
		public boolean isForkNode(SearchNodeWithData parentNode,
				Set<IState> subgoals) {
			if (parentNode.firstEffect == null)
				return true;
			return false;
		}
	}
}
